package pay.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.taxation.common.exceptions.MyException;
import com.taxation.web.config.PayProperties;
import com.taxation.web.project.pay.config.PayClient;
import com.taxation.web.project.pay.config.PayClientFactory;
import com.taxation.web.project.pay.enums.*;
import com.taxation.web.project.pay.mapper.PayOrderExtensionMapper;
import com.taxation.web.project.pay.mapper.PayOrderMapper;
import com.taxation.web.project.pay.mapper.PayRefundMapper;
import com.taxation.web.project.pay.module.PageResult;
import com.taxation.web.project.pay.module.PayOrderDO;
import com.taxation.web.project.pay.module.channel.PayAppDO;
import com.taxation.web.project.pay.module.channel.PayChannelDO;
import com.taxation.web.project.pay.module.notify.PayNotifyReqDTO;
import com.taxation.web.project.pay.module.notify.PayNotifyTaskCreateReqDTO;
import com.taxation.web.project.pay.module.notify.PayRefundNotifyRespDTO;
import com.taxation.web.project.pay.module.order.PayOrderExtensionDO;
import com.taxation.web.project.pay.module.refund.PayRefundCreateReqDTO;
import com.taxation.web.project.pay.module.refund.PayRefundDO;
import com.taxation.web.project.pay.module.refund.PayRefundUnifiedReqDTO;
import com.taxation.web.project.pay.module.refund.vo.PayRefundExportReqVO;
import com.taxation.web.project.pay.module.refund.vo.PayRefundPageReqVO;
import com.taxation.web.project.pay.service.*;
import com.taxation.web.project.weixin.mapper.ReportOrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 退款订单 Service 实现类
 *
 * @author wwy
 */
@Service
@Slf4j
@Validated
public class PayRefundServiceImpl implements PayRefundService {

    @Resource
    private PayProperties payProperties;

    @Resource
    private PayClientFactory payClientFactory;

    @Resource
    private PayRefundMapper refundMapper;
    @Resource
    private PayOrderMapper orderMapper;

    @Resource
    private PayOrderService orderService;
    @Resource
    private PayOrderExtensionMapper orderExtensionMapper;
    @Resource
    private PayAppService appService;
    @Resource
    private PayChannelService channelService;
    @Resource
    private PayNotifyService notifyService;

    @Resource
    private ReportOrderMapper reportOrderMapper;


    @Override
    public PayRefundDO getRefund(Long id) {
        return refundMapper.selectById(id);
    }

    @Override
    public PageResult<PayRefundDO> getRefundPage(PayRefundPageReqVO pageReqVO) {
        return null;
    }

    @Override
    public List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO) {
        return null;
    }

//    @Override
//    public PageResult<PayRefundDO> getRefundPage(PayRefundPageReqVO pageReqVO) {
//        return refundMapper.selectPage(pageReqVO);
//    }
//
//    @Override
//    public List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO) {
//        return refundMapper.selectList(exportReqVO);
//    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createPayRefund(PayRefundCreateReqDTO reqDTO) {
        // 获得 PayOrderDO
        PayOrderDO order = orderService.getOrder(reqDTO.getPayOrderId());
        // 校验订单是否存在
        if (Objects.isNull(order)) {
            throw new MyException(ErrorCodeConstants.PAY_ORDER_NOT_FOUND.getCode(), ErrorCodeConstants.PAY_ORDER_NOT_FOUND.getMsg());
        }

        // 校验 App
        PayAppDO app = appService.validPayApp(order.getAppId());
        // 校验支付渠道是否有效
        PayChannelDO channel = channelService.validPayChannel(order.getChannelId());
        // 校验支付客户端是否正确初始化
        PayClient client = payClientFactory.getPayClient(channel.getId());
        if (client == null) {
            log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
            throw new MyException(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND.getCode(), ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND.getMsg());
        }

        // 待实现
        String merchantRefundId = RandomUtil.randomNumbers(16);

        // 校验退款的条件
        validatePayRefund(reqDTO, order);
        // 退款类型
        PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
        if (Objects.equals(reqDTO.getAmount(), order.getAmount())) {
            refundType = PayRefundTypeEnum.ALL;
        }
        PayOrderExtensionDO orderExtensionDO = orderExtensionMapper.selectById(order.getSuccessExtensionId());
        PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
                merchantRefundId);
        if (Objects.nonNull(payRefundDO)) {
            // 退款订单已经提交过。
            if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
                    || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) {
                //已成功退款
                throw new MyException(ErrorCodeConstants.PAY_REFUND_SUCCEED.getCode(), ErrorCodeConstants.PAY_REFUND_SUCCEED.getMsg());
            }
            //可以重复提交，保证 退款请求号 一致，由渠道保证幂等
        } else {
            // 成功，插入退款单 状态为生成.没有和渠道交互
            payRefundDO = PayRefundDO.builder()
                    .appId(order.getAppId())
                    .channelOrderNo(order.getChannelOrderNo())
                    .channelCode(order.getChannelCode())
                    .channelId(order.getChannelId())
                    .merchantId(order.getMerchantId())
                    .orderId(order.getId())
                    .merchantRefundNo(merchantRefundId)
                    .notifyUrl(app.getRefundNotifyUrl())
                    .payAmount(order.getAmount())
                    .refundAmount(reqDTO.getAmount())
                    .userIp(reqDTO.getUserIp())
                    .merchantOrderId(order.getMerchantOrderId())
                    .tradeNo(orderExtensionDO.getNo())
                    .status(PayRefundStatusEnum.CREATE.getStatus())
                    .reason(reqDTO.getReason())
                    .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
                    .type(refundType.getStatus())
                    .build();
            refundMapper.insert(payRefundDO);
        }
        PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
        unifiedReqDTO.setUserIp(reqDTO.getUserIp())
                .setOriginAmount(reqDTO.getOriginAmount())
                .setAmount(reqDTO.getAmount())
                .setChannelOrderNo(order.getChannelOrderNo())
                .setPayTradeNo(orderExtensionDO.getNo())
                .setMerchantRefundId(merchantRefundId)
                .setNotifyUrl(genRefoundNotifyUrl(channel))
                .setReason(reqDTO.getReason());

        // 向渠道发起退款申请
        client.unifiedRefund(unifiedReqDTO);
        // 检查是否失败，失败抛出业务异常。
        // 成功在 退款回调中处理
        return payRefundDO.getId();
    }

    /**
     * 退款回调地址
     */
    private String genRefoundNotifyUrl(PayChannelDO channel) {
        return payProperties.getRefundUrl() + "/" + channel.getId();
    }

    @Override
    public void notifyPayRefund(String channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
//        // 校验支付渠道是否有效
        PayChannelDO channel = channelService.validPayChannel(channelId);
        // 更新退款订单
        getSelf().notifyRefund(channel, notify);

    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PayRefundServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }


    /**
     * 通知并更新订单的退款结果
     *
     * @param channel 支付渠道
     * @param notify  通知
     */
    @Transactional(rollbackFor = Exception.class)
    // 注意，如果是方法内调用该方法，需要通过 getSelf().notifyRefund(channel, notify) 调用，否则事务不生效
    public void notifyRefund(PayChannelDO channel, PayRefundNotifyRespDTO notify) {
        // 情况一：退款成功
        if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())) {
            notifyRefundSuccess(channel, notify);
            return;
        }
        // 情况二：退款失败
        if (Objects.equals(PayNotifyRefundStatusEnum.ABNORMAL, notify.getStatus())) {
            notifyRefundFailure(channel, notify);
        }
    }

    private void payRefundSuccess(PayRefundNotifyRespDTO refundNotify) {
        // 校验退款单存在
        PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
                refundNotify.getReqNo());
        if (refundDO == null) {
            log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
            throw new MyException(ErrorCodeConstants.PAY_REFUND_NOT_FOUND.getCode(), ErrorCodeConstants.PAY_REFUND_NOT_FOUND.getMsg());
        }

        // 得到已退金额
        PayOrderDO payOrderDO = orderService.getOrder(refundDO.getOrderId());
        Long refundedAmount = payOrderDO.getRefundAmount();

        PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
        if (Objects.equals(payOrderDO.getAmount(), refundedAmount + refundDO.getRefundAmount())) {
            //支付金额  = 已退金额 + 本次退款金额。
            orderStatus = PayOrderStatusEnum.CLOSED;
        }
        // 更新支付订单
        PayOrderDO updateOrderDO = new PayOrderDO();
        updateOrderDO.setRefundAmount(refundedAmount + refundDO.getRefundAmount())
                .setStatus(orderStatus.getStatus())
                .setRefundTimes(payOrderDO.getRefundTimes() + 1)
                .setRefundStatus(refundDO.getType())
                .setId(refundDO.getOrderId());
        orderMapper.updateById(updateOrderDO);

        // 更新退款订单
        PayRefundDO updateRefundDO = new PayRefundDO();
        updateRefundDO.setSuccessTime(refundNotify.getRefundSuccessTime())
                .setChannelRefundNo(refundNotify.getChannelOrderNo())
                .setTradeNo(refundNotify.getTradeNo())
                .setNotifyTime(LocalDateTime.now())
                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
                .setId(refundDO.getId());
        refundMapper.updateById(updateRefundDO);
//修改订单状态为已退款

        // 插入退款通知记录

        notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
                .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
    }


    private void notifyRefundSuccess(PayChannelDO channel, PayRefundNotifyRespDTO notify) {
        // 1.1 查询 PayRefundDO
        //PayRefundDO refund = refundMapper.selectByTradeNoAndMerchantRefundNo(notify.getTradeNo(),
        //         notify.getReqNo());
        PayRefundDO refund = refundMapper.selectByTradeNo(notify.getTradeNo());
        if (refund == null) {
            log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", notify.getReqNo());
            throw new MyException(ErrorCodeConstants.PAY_REFUND_NOT_FOUND.getCode(), ErrorCodeConstants.PAY_REFUND_NOT_FOUND.getMsg());
        }
        if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功，直接返回，不用重复更新
            log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功，无需更新]", refund.getId());
            return;
        }
        if (!PayRefundStatusEnum.CREATE.getStatus().equals(refund.getStatus())) {
            throw new MyException(ErrorCodeConstants.PAY_REFUND_NOT_CREATED.getCode(), ErrorCodeConstants.PAY_REFUND_NOT_CREATED.getMsg());
        }

        // 更新退款订单
        PayRefundDO updateRefundDO = new PayRefundDO();
        updateRefundDO.setSuccessTime(notify.getRefundSuccessTime())
                .setChannelRefundNo(notify.getChannelOrderNo())
                .setTradeNo(notify.getTradeNo())
                .setNotifyTime(LocalDateTime.now())
                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
                .setId(refund.getId());
        refundMapper.updateById(updateRefundDO);

        // 2. 更新订单
        updateOrderRefundPrice(refund.getOrderId(), refund.getRefundAmount());
        log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId());
        PayNotifyTaskCreateReqDTO reqDTO = new PayNotifyTaskCreateReqDTO();
        reqDTO.setType(PayNotifyTypeEnum.REFUND.getType());
        reqDTO.setDataId(refund.getId());
        notifyService.createPayNotifyTask(reqDTO);
    }


    private void updateOrderRefundPrice(String id, Long incrRefundPrice) {
        PayOrderDO order = orderMapper.selectById(id);
        if (order == null) {
            throw new MyException(ErrorCodeConstants.PAY_ORDER_NOT_FOUND.getCode(), ErrorCodeConstants.PAY_ORDER_NOT_FOUND.getMsg());
        }
        if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
            throw new MyException(ErrorCodeConstants.PAY_ORDER_REFUND_FAIL_STATUS_ERROR.getCode(), ErrorCodeConstants.PAY_ORDER_REFUND_FAIL_STATUS_ERROR.getMsg());
        }
        // 更新订单
        PayOrderDO updateObj = new PayOrderDO()
                .setRefundAmount(incrRefundPrice)
                .setRefundStatus(PayRefundTypeEnum.ALL.getStatus())
                .setRefundTimes(1)
                .setStatus(PayOrderStatusEnum.CLOSED.getStatus());
        int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj);
        if (updateCount == 0) {
            throw new MyException(ErrorCodeConstants.PAY_ORDER_REFUND_FAIL_STATUS_ERROR.getCode(), ErrorCodeConstants.PAY_ORDER_REFUND_FAIL_STATUS_ERROR.getMsg());
        }
        log.info("[updatePayOrderSuccess][支付订单({}) 更新为已支付]", order.getId());
    }

    private void notifyRefundFailure(PayChannelDO channel, PayRefundNotifyRespDTO notify) {
        // 1.1 查询 PayRefundDO
        PayRefundDO refund = refundMapper.selectByTradeNoAndMerchantRefundNo(notify.getTradeNo(),
                notify.getReqNo());
        if (refund == null) {
            log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", notify.getReqNo());
            throw new MyException(ErrorCodeConstants.PAY_REFUND_NOT_FOUND.getCode(), ErrorCodeConstants.PAY_REFUND_NOT_FOUND.getMsg());
        }
        if (PayRefundStatusEnum.isFailure(refund.getStatus())) {
            log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭，无需更新]", refund.getId());
            return;
        }
        if (!PayRefundStatusEnum.CREATE.getStatus().equals(refund.getStatus())) {
            throw new MyException(ErrorCodeConstants.PAY_REFUND_NOT_CREATED.getCode(), ErrorCodeConstants.PAY_REFUND_NOT_CREATED.getMsg());
        }

        PayRefundDO updateRefundDO = new PayRefundDO();
        updateRefundDO.setSuccessTime(notify.getRefundSuccessTime())
                .setChannelRefundNo(notify.getChannelOrderNo())
                .setTradeNo(notify.getTradeNo())
                .setNotifyTime(LocalDateTime.now())
                .setStatus(PayRefundStatusEnum.FAILURE.getStatus())
                .setId(refund.getId());
        refundMapper.updateById(updateRefundDO);

        log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId());
        // 2. 插入退款通知记录
        PayNotifyTaskCreateReqDTO reqDTO = new PayNotifyTaskCreateReqDTO();
        reqDTO.setType(PayNotifyTypeEnum.REFUND.getType());
        reqDTO.setDataId(refund.getId());
        notifyService.createPayNotifyTask(reqDTO);
        //notifyService.createPayNotifyTask(PayNotifyTypeEnum.REFUND.getType(), refund.getId());
    }

    /**
     * 校验是否进行退款
     *
     * @param reqDTO 退款申请信息
     * @param order  原始支付订单信息
     */
    private void validatePayRefund(PayRefundCreateReqDTO reqDTO, PayOrderDO order) {
        // 校验状态，必须是支付状态
        if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
            throw new MyException(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS.getCode(), ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS.getMsg());
        }
        // 是否已经全额退款
        if (PayRefundTypeEnum.ALL.getStatus().equals(order.getRefundStatus())) {
            throw new MyException(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED.getCode(), ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED.getMsg());
        }
        // 校验金额 退款金额不能大于 原定的金额
        if (reqDTO.getAmount() + order.getRefundAmount() > order.getAmount()) {
            throw new MyException(ErrorCodeConstants.PAY_REFUND_AMOUNT_EXCEED.getCode(), ErrorCodeConstants.PAY_REFUND_AMOUNT_EXCEED.getMsg());
        }
        // 校验渠道订单号
        if (StrUtil.isEmpty(order.getChannelOrderNo())) {
            throw new MyException(ErrorCodeConstants.PAY_REFUND_CHN_ORDER_NO_IS_NULL.getCode(), ErrorCodeConstants.PAY_REFUND_CHN_ORDER_NO_IS_NULL.getMsg());
        }
    }

}
